#ifndef RINGBUF_H_INCLUDED
#define RINGBUF_H_INCLUDED
/******************************************************************************
* (c)2019-2025 Michael T. Richter
*
* This software is distributed under the terms of WTFPLv2.  The full terms and
* text of the license can be found at http://www.wtfpl.net/txt/copying
******************************************************************************/
/*******************************************************************************
* RINGBUF - a simple ring buffer for arbitrary data.
*
* The ring buffers of this library are created by calls to ringbuf_create().
* They can be self-managed (malloc their own buffer) or externally managed
* (buffer supplied on creation) to support strongly static or strongly dynamic
* environments.  Each call to ringbuf_create() needs to be paired with a call
* to ringbuf_destroy to ensure all memory is properly cleaned.
*
* Items are copied into the buffer bitwise and can be inserted at the front
* with ringbuf_insert()/ringbuf_finsert() or the back with ringbuf_binsert() as
* desired.  Note that there is no protection from overflowing the ring.  If
* max_items + 1 items are inserted into the ring buffer before any removals the
* ring will hold max_items one item and all initial items will be lost.  Calls
* to ringbuf_count() and ringbuf_maxitems should be used to guard against this.
*
* Items are returned by pointer from the back with
* ringbuf_remove()/ringbuf_bremove() or from the front with ringbuf_fremove().
* Removed items should be used immediately or copied into safe storage before
* any other ringbuf operations.  Attempting to remove from an empty buffer
* results in returning the NULL pointer.  The ringbuf_isempty() predicate can be
* used as well to detect this.
*
* Most users will use the ringbuf as a queue-like structure using
* ringbuf_insert()/ringbuf_remove().  Inserting at the front, however, while
* also removing from the front, or doing both operations at the back, results in
* a stack-like operation.  (Using combinations can result in complex data
* usage scenarios like a dual stack or a queue with temporary stack
* capabilities.)
*
* If during operations the ring buffer needs to be grown or shrunk, this can be
* accomplished with the ringbuf_resize() operation.
*******************************************************************************/

#include <stdbool.h>
#include <stdlib.h>

#ifndef UNUSED
#define UNUSED(X) ((void)X)
#endif

typedef void *ring_h;

typedef void (*ringbuf_error_handler_t)(const char *file, const int line, const char *error_message);
extern ringbuf_error_handler_t ringbuf_error_handler;

/*
 * Insert an item into the ring buffer at the back pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 * item -> the opaque pointer to the item to be copied into the buffer.
 *
 * NOTE: INSERTING MORE THAN THE MAXIMUM NUMBER OF ITEMS WILL CAUSE EARLIER
 * ITEMS TO SIMPLY DISAPPEAR!
 */
void ringbuf_binsert(ring_h ring, void const *item);

/*
 * Remove an item from the ring buffer at the back pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 *
 * Returns a pointer to the item removed.
 *
 * NOTE: the returned item should be used and/or copied away before any further
 *       use of the ring buffer.
 */
void const *ringbuf_bremove(ring_h ring);

/*
 * Clear the contents of the ring buffer and reset operations.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 */
void ringbuf_clear(ring_h ring);

/*
 * Make a deep copy of a ringbuf.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 * buffer -> optional pointer to new buffer.
 *
 * Returns the new ringbuf.
 *
 * Note: the new ringbuf may be self-managed.
 * Note: if the provided buffer is not of sufficient size bad things will
 *       happen.
 */
ring_h ringbuf_clone(const ring_h, void *);

/*
 * Get the number of items stored in the ring buffer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 *
 * Returns the number of items stored.
 */
size_t ringbuf_count(const ring_h ring);

/*
 * Create a ring buffer with count items of size item_size.
 *
 * buffer -> the storage space for the ring buffer.
 * item_size -> the byte width of the items to be stored.
 * count -> the maximum number of items to permit in the ring buffer.
 *
 * Returns an opaque pointer to the ringbuf as a handle.
 *
 * NOTE: if buffer is NULL then the ring buffer is self-managing and will malloc
 *       and free memory as needed.
 */
ring_h ringbuf_create(volatile void *buffer, size_t item_size, size_t count);

/*
 * Destroy an instance of the ring buffer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer
 *
 * NOTE: if the ring buffer is self-managed it will delete its own buffer.
 * NOTE: in all cases it will delete memory allocated during ringbuf_create().
 */
void ringbuf_destroy(ring_h ring);

/*
 * Insert an item into the ring buffer at the front pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer
 * item -> the opaque pointer to the item to be copied into the buffer.
 *
 * NOTE: INSERTING MORE THAN THE MAXIMUM NUMBER OF ITEMS WILL CAUSE EARLIER
 * ITEMS TO SIMPLY DISAPPEAR!
 */
void ringbuf_finsert(ring_h ring, void const *item);

/*
 * Remove an item from the ring buffer at the front pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 *
 * Returns a pointer to the item removed.
 *
 * NOTE: the returned item should be used and/or copied away before any further
 *       use of the ring buffer.
 */
void const *ringbuf_fremove(ring_h ring);

/*
 * Insert an item into the ring buffer at the front pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer
 * item -> the opaque pointer to the item to be copied into the buffer.
 *
 * NOTE: INSERTING MORE THAN THE MAXIMUM NUMBER OF ITEMS WILL CAUSE EARLIER
 * ITEMS TO SIMPLY DISAPPEAR!
 *
 * Note: this is an alias for ringbuf_finsert().
 */
void ringbuf_insert(ring_h ring, void const *item);

/*
 * Check for an empty buffer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer
 *
 * Return true if the buffer is empty, false otherwise.
 */
bool ringbuf_isempty(const ring_h ring);

/*
 * Get the maximum number of items possible in the ring buffer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 *
 * Returns the maximum number of items which can be stored.
 */
size_t ringbuf_maxitems(const ring_h ring);

/*
 * Remove an item from the ring buffer at the back pointer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 *
 * Returns a pointer to the item removed.
 *
 * NOTE: the returned item should be used and/or copied away before any further
 *       use of the ring buffer.
 *
 * NOTE: this is an alias for ringbuf_bremove().
 */
void const *ringbuf_remove(ring_h ring);

/*
 * Resize a ring buffer.
 *
 * ring -> the opaque pointer (handle) to the ring buffer.
 * buffer -> the new storage space for the ring buffer.
 * count -> the new maximum number of items to permit in the ring buffer.
 *
 * Returns the new ring buffer handle.
 *
 * NOTE: self-managed ring buffers may become externally-managed and vice versa
 *       using this operation.
 * NOTE: the item size cannot be changed.
 * NOTE: a resized ring buffer will copy its contents into the new buffer, but
 *       will lose data if the new buffer is smaller than the number of current
 *       items.
 */
ring_h ringbuf_resize(ring_h ring, void *buffer, size_t count);

#ifdef RINGBUF_IMPLEMENTATION
/*******************************************************************************
* RINGBUF IMPLEMENTATION SECTION
* ==============================
*
* In a single source file in your project, the symbol RINGBUF_IMPLEMENTATION
* must be defined before including the header:
*
*   ```c
*   #define RINGBUF_IMPLEMENTATION
*   #include "ringbuf.h"
*   ```
*
* All other source files using this library must *not* define this symbol.
*******************************************************************************/

#include <stdint.h>
#include <string.h>

/*******************************************************************************
* DATA STRUCTURES
*******************************************************************************/

/***************************************
* A RingBuffer contains a pointer to a block of bytes (buffer), the size of the
* items stored in said block (item_size), and a maximum count of the items it
* can store (max_items).  The buffer can be externally supplied
* (self_managed = false) or it can be allocated by the buffer code itself
* (self_managed = true).  In addition * it has two indices for the front and
back insertion/removal points (front, back).
***************************************/
typedef struct _ring_buffer
{
    bool    self_managed: 1, empty: 1;
    volatile size_t  front, back;
    size_t  max_items, item_size;
    volatile uint8_t *buffer;
} RingBuffer;

#ifdef DEBUG
#include <stdio.h>
#define TF(X) ((X) ? "true" : "false")
static void dump_ring(const RingBuffer *r);
static void dump_ring(const RingBuffer *r)
{
    printf("\n");
    printf("\tself_managed = %s\n", TF(r->self_managed));
    printf("\tempty        = %s\n", TF(r->empty));
    printf("\tfront        = %d\n", r->front);
    printf("\tback         = %d\n", r->back);
    printf("\tmax_items    = %d\n", r->max_items);
    printf("\titem_size    = %d\n", r->item_size);
    printf("\n");
}
#else
#define dump_ring(X)
#endif

size_t ringbuf_allocsize()
{
    return sizeof(RingBuffer);
}

void ringbuf_binsert(ring_h ring, void const *item)
{
    RingBuffer *r = ring;
    uint8_t const *i = item;
    volatile uint8_t *l = NULL;

    size_t const f = r->front;
    size_t const b = r->back;

    if ((f == b) && !r->empty)
    {
        if (r->front)
        {
            --r->front;
        }
        else
        {
            r->front = r->max_items;
        }
    }

    if (r->back == 0)
    {
        r->back = r->max_items;
    }

    l = r->buffer + --r->back * r->item_size;

    memcpy((void *)l, i, r->item_size);
    r->empty = false;
}

const void *ringbuf_bremove(ring_h ring)
{
    RingBuffer *r = ring;
    volatile uint8_t const *i;

    if (ringbuf_isempty(ring))
    {
        i = NULL;
    }
    else
    {
        i = r->buffer + r->back++ * r->item_size;
    }

    if (r->back >= r->max_items)
    {
        r->back = 0;
    }

    size_t f = r->front;
    size_t b = r->back;

    if (f == b)
    {
        r->empty = true;
    }

    return (const void *)i;
}

void ringbuf_clear(ring_h ring)
{
    RingBuffer *r = ring;
    memset((void *)r->buffer, 0, r->max_items * r->item_size);
    r->front = r->back = 0;
    r->empty = true;
}

ring_h ringbuf_clone(const ring_h ring, void *buffer)
{
    RingBuffer *o = ring;
    RingBuffer *r = malloc(sizeof(RingBuffer));
    if (!r)
    {
        ringbuf_error_handler(__FILE__, __LINE__, "could not allocate new ring_h object");
        return NULL;
    }
    memcpy(r, o, sizeof(RingBuffer));

    if (buffer)
    {
        r->self_managed = false;
        r->buffer = buffer;
    }
    else
    {
        r->self_managed = true;
        r->buffer = malloc(o->item_size * o->max_items);
        if (!r->buffer)
        {
            free(r);
            ringbuf_error_handler(__FILE__, __LINE__, "could not allocate clone buffer");
            return NULL;
        }
    }

    memcpy((void *)r->buffer, (void *)o->buffer, o->item_size * o->max_items);

    return r;
}

size_t ringbuf_count(const ring_h ring)
{
    RingBuffer const *r = ring;
    size_t count;

    size_t f = r->front;
    size_t b = r->back;

    if (r->empty)
    {
        count = 0;
    }
    else if (f == b)
    {
        count = r->max_items;
    }
    else if (f >= b)
    {
        count = f - b;
    }
    else
    {
        count = r->max_items - b + f;
    }

    return count;
}

ring_h ringbuf_create(volatile void *buffer, size_t item_size, size_t count)
{
    if (item_size == 0)
    {
        ringbuf_error_handler(__FILE__, __LINE__, "invalid item size");
        return NULL;
    }
    if (count == 0)
    {
        ringbuf_error_handler(__FILE__, __LINE__, "invalid item count");
        return NULL;
    }

    RingBuffer *r = malloc(sizeof(RingBuffer));
    if (!r)
    {
        ringbuf_error_handler(__FILE__, __LINE__, "could not allocate new ring_h");
        return NULL;
    }

    if (buffer)
    {
        r->self_managed = false;
        r->buffer = buffer;
    }
    else
    {
        r->self_managed = true;
        r->buffer = malloc(item_size * count);
        if (!r->buffer)
        {
            free(r);
            ringbuf_error_handler(__FILE__, __LINE__, "could not allocate new buffer");
            return NULL;
        }
    }

    r->item_size = item_size;
    r->max_items = count;

    ringbuf_clear(r);

    return r;
}

void ringbuf_destroy(ring_h ring)
{
    RingBuffer *r = ring;

    if (r->self_managed)
    {
        free((void *)r->buffer);
    }

    free(r);
}

void ringbuf_finsert(ring_h ring, void const *item)
{
    RingBuffer *r = ring;
    uint8_t const *i = item;
    volatile uint8_t *l;

    size_t f = r->front;
    size_t b = r->back;

    if (f == b && !r->empty)
    {
        ++r->back;

        if (r->back >= r->max_items)
        {
            r->back = 0;
        }
    }

    l = r->buffer + r->front++ * r->item_size;

    memcpy((void *)l, i, r->item_size);

    if (r->front >= r->max_items)
    {
        r->front = 0;
    }

    r->empty = false;
}

const void *ringbuf_fremove(ring_h ring)
{
    RingBuffer *r = ring;
    volatile uint8_t const *i;

    if (ringbuf_isempty(ring))
    {
        i = NULL;
    }
    else
    {
        if (r->front == 0)
        {
            r->front = r->max_items;
        }

        i = r->buffer + --r->front * r->item_size;
    }

    size_t f = r->front;
    size_t b = r->back;

    if (b == f)
    {
        r->empty = true;
    }

    return (const void *)i;
}
void ringbuf_insert(ring_h ring, void const *item)
{
    ringbuf_finsert(ring, item);
}

bool ringbuf_isempty(const ring_h ring)
{
    RingBuffer const *r = ring;

    return r->empty;
}

size_t ringbuf_maxitems(const ring_h ring)
{
    RingBuffer const *r = ring;

    return r->max_items;
}

const void *ringbuf_remove(ring_h ring)
{
    return ringbuf_bremove(ring);
}

ring_h ringbuf_resize(ring_h ring, void *buffer, size_t count)
{
    RingBuffer *o = ring;
    RingBuffer *r = ringbuf_create(buffer, o->item_size, count);
    if (!r)
    {
        return NULL;
    }
    const size_t c = ringbuf_count(o);

    for (size_t i = 0; i < c; i++)
    {
        ringbuf_insert(r, ringbuf_remove(o));
    }

    if (o->self_managed)
    {
        free((void *)o->buffer);
    }

    memcpy(o, r, sizeof(RingBuffer));
    free(r);
    return o;
}

/*******************************************************************************
* Static helper function definitions
*******************************************************************************/

void default_ringbuf_error_handler(const char *file, const int line, const char *error_message)
{
    UNUSED(file);
    UNUSED(line);
    UNUSED(error_message);
    while (true) {}
}
ringbuf_error_handler_t ringbuf_error_handler = default_ringbuf_error_handler;

#endif //RINGBUF_IMPLEMENTATION

#endif /*RINGBUF_H_INCLUDED*/
